﻿using GoogleSpeechRecognition;
using WordCloudGenerator.TextAnalyses.Blacklist;
using WordCloudGenerator.TextAnalyses.Blacklist.En;
using WordCloudGenerator.TextAnalyses.Processing;
using WordCloudGenerator.TextAnalyses.Stemmers;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Drawing.Text;
using WordCloudGenerator.Geometry;
using System.Drawing.Imaging;
using System.Reflection;
using System.IO;

namespace WordCloudInterface
{
    public partial class                        Interface : Form
    {
        #region Attributes and Constants

        private const double                    SPEECH_RECOGNITION_CONFIDENCE = 0.00;

        private Toolbox                         toolbox = null;
        
        private bool                            stackImports = false;

        private string                          language;

        private int                             occurenceLimit = 0;
        private IEnumerable<IWord>              saveFilteredByOccurence = null;

        private StatusBar                       mainStatusBar;
        private StatusBarPanel                  statusPanel;

        #endregion

        #region Constructor

        public                                  Interface()
        {
            InitializeComponent();

            // Change application's upper-left icon
            this.Icon = GetProgramIcon();

            // Create Menu
            this.CreateMenu();

            // Set event handlers
            this.cloudControl.Click += new System.EventHandler(this.WordCloud_Click);

            // Configure status bar
            mainStatusBar = new StatusBar();
            statusPanel = new StatusBarPanel();

            statusPanel.BorderStyle = StatusBarPanelBorderStyle.Sunken;
            statusPanel.Text = "Application started. No action yet.";
            statusPanel.ToolTipText = "Last Activity";
            statusPanel.AutoSize = StatusBarPanelAutoSize.Spring;
            mainStatusBar.Panels.Add(statusPanel);

            mainStatusBar.Dock = DockStyle.Bottom;
            mainStatusBar.ShowPanels = true;

            this.Controls.Add(mainStatusBar);

            // Initialize language
            this.language = "en-US";
            this.GetMenuItemByName("English (US)").Checked = true;
        }

        #endregion

        #region Private Methods

        private void                            CreateMenu()
        {
            this.Menu = new MainMenu();
            MenuItem it1 = new MenuItem("Generate");
            MenuItem it2 = new MenuItem("From audio file");
            it2.Click += OpenAudioFile_Click;
            MenuItem it3 = new MenuItem("From microphone");
            it3.Click += RecordMicrophone_Click;
            MenuItem it4 = new MenuItem("From text file");
            it4.Click += OpenTxtFile_Click;
            MenuItem it5 = new MenuItem("From text input");
            it5.Click += FromTextInput_Click;
            it1.MenuItems.Add(it2);
            it1.MenuItems.Add(it3);
            it1.MenuItems.Add(it4);
            it1.MenuItems.Add(it5);

            MenuItem it6 = new MenuItem("Export");
            List<Tuple<string, ImageFormat>> imgFormats = GetImageFormats();
            foreach (Tuple<string, ImageFormat> imgFormat in imgFormats)
            {
                MenuItem tmpItem = new MenuItem(imgFormat.Item1);
                tmpItem.Click += Export_Click;
                it6.MenuItems.Add(tmpItem);
            }

            MenuItem it9 = new MenuItem("Options");
            MenuItem it10 = new MenuItem("Open tool box");
            it10.Click += Opentoolbox_Click;
            MenuItem it11 = new MenuItem("Add word");
            it11.Click += AddNewWordManually_Click;
            MenuItem it12 = new MenuItem("Add background image");
            it12.Click += AddBackgroundImage_Click;
            MenuItem RemoveBg = new MenuItem("Remove backgroud image");
            RemoveBg.Click += RemoveBackgroundImage_Click;
            RemoveBg.Visible = false;
            it9.MenuItems.Add(it10);
            it9.MenuItems.Add(it11);
            it9.MenuItems.Add(it12);
            it9.MenuItems.Add(RemoveBg);

            MenuItem it13 = new MenuItem("Language");
            MenuItem enLanguage = new MenuItem("English (UK)");
            enLanguage.Click += EnLanguage_Click;
            MenuItem usLanguage = new MenuItem("English (US)");
            usLanguage.Click += UsLanguage_Click;
            MenuItem frLanguage = new MenuItem("Français");
            frLanguage.Click += FrLanguage_Click;
            it13.MenuItems.Add(enLanguage);
            it13.MenuItems.Add(usLanguage);
            it13.MenuItems.Add(frLanguage);

            MenuItem it14 = new MenuItem("Dictionnaries");
            MenuItem it15 = new MenuItem("Import");
            it15.Click += ImportDictionnary_Click;
            it14.MenuItems.Add(it15);

            MenuItem it16 = new MenuItem("About");
            it16.Click += About_Click;
            
            this.Menu.MenuItems.Add(it1);
            this.Menu.MenuItems.Add(it6);
            this.Menu.MenuItems.Add(it9);
            this.Menu.MenuItems.Add(it13);
            this.Menu.MenuItems.Add(it14);
            this.Menu.MenuItems.Add(it16);
        }


        private void                            ProcessText(string text, bool isWordManuallyAdded = false)
        {
            if (text != null && text.Length > 0)
            {
                if (isWordManuallyAdded == false)
                {
                    if (this.StackImports == true)
                    {
                        string fullTxt = cloudControl.SaveText + text;
                        text = fullTxt;
                    }
                }
                cloudControl.SaveText = text;

                IBlacklist blacklist = new CommonWords();
                //IBlacklist customBlacklist = CommonBlacklist.CreateFromTextFile("blacklist.txt");

                InputType inputType = InputType.String;
                IEnumerable<string> terms = ComponentFactory.CreateExtractor(inputType, text);
                IWordStemmer stemmer = ComponentFactory.CreateWordStemmer(true);

                IEnumerable<IWord> words = terms.Filter(blacklist).CountOccurences(); // terms.Filter(blacklist).Filter(customBlacklist).CountOccurences();
                IEnumerable<IWord> wordsTmp = words.GroupByStem(stemmer).SortByOccurences().Cast<IWord>();
                if (this.OccurenceLimit > 0)
                    cloudControl.WeightedWords = wordsTmp.FilterByOccurences(this.OccurenceLimit).Cast<IWord>();
                else
                    cloudControl.WeightedWords = wordsTmp;
            }
        }

        private MenuItem                        RecursiveGetMenuItem(MenuItem current, string name)
        {
            MenuItem result;

            if (current.Text.CompareTo(name) == 0)
                return (current);
            if (current.MenuItems.Count > 0)
            {
                Menu.MenuItemCollection items = current.MenuItems;
                foreach (MenuItem it in items)
                {
                    result = RecursiveGetMenuItem(it, name);
                    if (result != null)
                        return (result);
                }
            }
            return (null);
        }

        #endregion

        #region Menu Events

        private void                            OpenAudioFile_Click(object sender, EventArgs e)
        {
            // Select wav file
            OpenFileDialog openFileDialog1 = new OpenFileDialog();
            openFileDialog1.InitialDirectory = System.Windows.Forms.Application.StartupPath;
            openFileDialog1.Filter = "WAV Files (*.wav)|*.wav|MP3 Files (*.mp3)|*.mp3";
            openFileDialog1.FilterIndex = 1;
            openFileDialog1.RestoreDirectory = true;

            if (openFileDialog1.ShowDialog() != DialogResult.OK)
            {
                this.statusPanel.Text = "Invalid file selected.";
                return;
            }

            // Show status
            this.statusPanel.Text = "Processing...";

            try
            {
                // Recognize audio
                var stt = new SpeechToText(this.Language);
                var response = stt.Recognize(openFileDialog1.FileName);
                string text = "";
                foreach (TextResponse txt in response)
                {
                    if (txt.Confidence >= SPEECH_RECOGNITION_CONFIDENCE)
                        text += txt.Utterance + " ";
                }

                // Generate word cloud
                ProcessText(text);
                this.statusPanel.Text = "Processing done!";
            }
            catch
            {
                this.statusPanel.Text = "Google server is not replying (or maybe your audio file is corrupted)... Please retry.";
            }
        }

        private void                            RecordMicrophone_Click(object sender, EventArgs e)
        {
            RecordFromMicrophone    rfm = new RecordFromMicrophone(this, SPEECH_RECOGNITION_CONFIDENCE);
            rfm.ShowDialog();
        }

        private void                            OpenTxtFile_Click(object sender, EventArgs e)
        {
            // Select txt file
            OpenFileDialog openFileDialog1 = new OpenFileDialog();
            openFileDialog1.InitialDirectory = System.Windows.Forms.Application.StartupPath;
            openFileDialog1.Filter = "Text files (*.txt)|*.txt";
            openFileDialog1.FilterIndex = 1;
            openFileDialog1.RestoreDirectory = true;

            if (openFileDialog1.ShowDialog() != DialogResult.OK)
            {
                this.statusPanel.Text = "Invalid file selected.";
                return;
            }

            // Show status
            this.statusPanel.Text = "Processing...";

            try
            {
                // Retrieve txt file content
                System.IO.StreamReader myFile = new System.IO.StreamReader(openFileDialog1.FileName);
                string text = myFile.ReadToEnd();
                text += " ";
                myFile.Close();

                // Generate word cloud
                ProcessText(text);
                this.statusPanel.Text = "Processing done.";
            }
            catch
            {
                this.statusPanel.Text = "Invalid text file.";
            }
        }

        private void                            FromTextInput_Click(object sender, EventArgs e)
        {
            InputText inputTxt = new InputText(this);
            inputTxt.ShowDialog();
        }

        private void                            Export_Click(object sender, EventArgs e)
        {
            MenuItem realSender = (MenuItem)sender;

            using (FolderBrowserDialog dialog = new FolderBrowserDialog())
            {
                dialog.Description = "Choose where you want to save the picture";
                dialog.ShowNewFolderButton = false;
                dialog.RootFolder = Environment.SpecialFolder.MyComputer;
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    string folder = dialog.SelectedPath;

                    int width = this.cloudControl.Size.Width;
                    int height = this.cloudControl.Size.Height;

                    Bitmap bm = new Bitmap(width, height);
                    this.cloudControl.DrawToBitmap(bm, new Rectangle(0, 0, width, height));

                    List<Tuple<string, ImageFormat>> imgFormats = GetImageFormats();
                    foreach (Tuple<string, ImageFormat> imgFormat in imgFormats)
                    {
                        if (imgFormat.Item1.CompareTo(realSender.Text) == 0)
                        {
                            string outputPath = folder + "WordCloudOutput." + imgFormat.Item1.ToLower();
                            int i = 0;
                            while (File.Exists(outputPath))
                            {
                                ++i;
                                outputPath = folder + "WordCloudOutput" + Convert.ToString(i) + "." + imgFormat.Item1.ToLower();
                            }
                            bm.Save(outputPath, imgFormat.Item2);
                        }
                    }
                }
            }
        }

        private void                            AddNewWordManually_Click(object sender, EventArgs e)
        {
            AddWord aw = new AddWord(this);
            aw.ShowDialog();
        }

        private void                            Opentoolbox_Click(object sender, EventArgs e)
        {
            MenuItem                            realSender = (MenuItem)sender;

            if (realSender.Checked == false)
            {
                if (this.toolbox != null)
                {
                    this.toolbox.Dispose();
                    this.toolbox.Close();
                    this.toolbox = null;
                }
                this.toolbox = new Toolbox(this);
                this.toolbox.Show();
                realSender.Checked = true;
            }
            else
            {
                this.toolbox.Dispose();
                this.toolbox.Close();
                this.toolbox = null;
                realSender.Checked = false;
            }
        }

        private void                            AddBackgroundImage_Click(object sender, EventArgs e)
        {
            // Select txt file
            OpenFileDialog openFileDialog1 = new OpenFileDialog();
            openFileDialog1.InitialDirectory = System.Windows.Forms.Application.StartupPath;
            openFileDialog1.Filter = "JPEG Files (*.jpeg)|*.jpeg|PNG Files (*.png)|*.png|JPG Files (*.jpg)|*.jpg|GIF Files (*.gif)|*.gif";
            openFileDialog1.FilterIndex = 1;
            openFileDialog1.RestoreDirectory = true;

            if (openFileDialog1.ShowDialog() != DialogResult.OK)
            {
                this.statusPanel.Text = "Invalid file selected.";
                return;
            }

            // Set background picture
            try
            {
                Image img = Image.FromFile(openFileDialog1.FileName);
                this.cloudControl.BackgroundImage = img;
                this.cloudControl.BackgroundImageLayout = ImageLayout.Stretch;
                MenuItem removeBg = this.GetMenuItemByName("Remove backgroud image");
                if (removeBg != null)
                    removeBg.Visible = true;
            }
            catch
            {
                this.statusPanel.Text = "Invalid file selected.";
            }
        }

        private void                            RemoveBackgroundImage_Click(object sender, EventArgs e)
        {
            MenuItem                            it = (MenuItem)sender;

            if (this.cloudControl.BackgroundImage != null)
                this.cloudControl.BackgroundImage = null;
            it.Visible = false;
        }

        private void ImportDictionnary_Click(object sender, EventArgs e)
        {
            // Select txt file
            OpenFileDialog openFileDialog1 = new OpenFileDialog();
            openFileDialog1.InitialDirectory = System.Windows.Forms.Application.StartupPath;
            openFileDialog1.Filter = "Text files (*.txt)|*.txt";
            openFileDialog1.FilterIndex = 1;
            openFileDialog1.RestoreDirectory = true;

            if (openFileDialog1.ShowDialog() != DialogResult.OK)
            {
                this.statusPanel.Text = "Invalid file selected.";
                return;
            }

            // Show status
            this.statusPanel.Text = "Adding custom filter dictionnary...";

            try
            {
                // Retrieve txt file content
                System.IO.StreamReader myFile = new System.IO.StreamReader(openFileDialog1.FileName);
                string text = myFile.ReadToEnd();
                text += " ";
                myFile.Close();

                // Generate word cloud
                // ProcessText(text);
                string[] words = text.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                IEnumerable<IWord> current = this.cloudControl.WeightedWords;
                List<IWord> newwords = new List<IWord>();

                foreach (IWord w in current)
                {
                    bool found = false;
                    foreach (string s in words)
                    {
                        if (w.Text.CompareTo(s) == 0)
                            found = true;
                    }
                    if (found == false)
                        newwords.Add(w);
                }
                this.cloudControl.WeightedWords = newwords;

                this.statusPanel.Text = "Custom dictionnary successfully added to the filters!";
            }
            catch
            {
                this.statusPanel.Text = "Invalid dictionnary.";
            }
        }

        private void EnLanguage_Click(object sender, EventArgs e)
        {
            MenuItem m = ((MenuItem)sender);

            if (m.Checked == false)
            {
                m.Checked = true;
                this.GetMenuItemByName("English (US)").Checked = false;
                this.GetMenuItemByName("Français").Checked = false;
                this.language = "en-EN";
            }
        }

        private void UsLanguage_Click(object sender, EventArgs e)
        {
            MenuItem m = ((MenuItem)sender);

            if (m.Checked == false)
            {
                m.Checked = true;
                this.GetMenuItemByName("English (UK)").Checked = false;
                this.GetMenuItemByName("Français").Checked = false;
                this.language = "en-US";
            }
        }

        private void FrLanguage_Click(object sender, EventArgs e)
        {
            MenuItem m = ((MenuItem)sender);

            if (m.Checked == false)
            {
                m.Checked = true;                
                this.GetMenuItemByName("English (UK)").Checked = false;
                this.GetMenuItemByName("English (US)").Checked = false;
                this.language = "fr-FR";
            }
        }

        private void                            About_Click(object sender, EventArgs e)
        {
            About abt = new About();
            abt.ShowDialog();
        }

        #endregion

        #region Word Cloud Click Event

        private void                            WordCloud_Click(object sender, EventArgs e)
        {
            LayoutItem itemUderMouse;
            Point mousePositionRelativeToControl = cloudControl.PointToClient(new Point(MousePosition.X, MousePosition.Y));

            if (!this.cloudControl.TryGetItemAtLocation(mousePositionRelativeToControl, out itemUderMouse))
                return;
            CustomizeWord customizeWindow = new CustomizeWord(this, itemUderMouse.Word);
            customizeWindow.ShowDialog();
        }

        #endregion

        #region Public Methods (Accessor functions)

        public WordCloudGenerator.CloudControl  GetWordCloud()
        {
            return (this.cloudControl);
        }

        public MenuItem                         GetMenuItemByName(string name)
        {
            Menu.MenuItemCollection items = this.Menu.MenuItems;
            MenuItem result;

            foreach (MenuItem it in items)
            {
                result = this.RecursiveGetMenuItem(it, name);
                if (result != null)
                    return (result);
            }
            return (null);
        }

        public void                             CloseToolBox()
        {
            this.GetMenuItemByName("Open tool box").Checked = false;
        }

        public bool                             StackImports
        {
            get
            {
                return (this.stackImports);
            }
            set
            {
                this.stackImports = value;
            }

        }

        public int                              OccurenceLimit
        {
            get
            {
                return (this.occurenceLimit);
            }
            set
            {
                this.occurenceLimit = value;
                // We just make sure that each time we filter the original word list (to not loose our original word cloud).
                if (this.saveFilteredByOccurence != null)
                {
                    this.cloudControl.WeightedWords = this.saveFilteredByOccurence;
                }
                if (this.occurenceLimit > 0 && this.cloudControl.WeightedWords != null && this.cloudControl.WeightedWords.Count() > 0)
                {
                    this.saveFilteredByOccurence = this.cloudControl.WeightedWords;
                    this.cloudControl.WeightedWords = this.cloudControl.WeightedWords.FilterByOccurences(this.occurenceLimit);
                }
            }
        }

        public string Language
        {
            get
            {
                return (this.language);
            }
            set
            {
                this.language = value;
            }
        }

        #endregion

        #region Public Methods (Should only be accessed by the CustomizeWord form)

        public void                             FromWordCloud_DeleteWord(IWord word)
        {
            IEnumerable<IWord> currentWords = cloudControl.WeightedWords;
            List<IWord> newWords = new List<IWord>();

            foreach (IWord w in currentWords)
            {
                if (word.Text.CompareTo(w.Text) != 0)
                    newWords.Add(w);
            }
            cloudControl.WeightedWords = newWords;
            cloudControl.Invalidate();
        }

        public void                             FromWordCloud_EditWord(IWord word, string newWord, int occurence)
        {
            IEnumerable<IWord> currentWords = cloudControl.WeightedWords;
            List<IWord> newWords = new List<IWord>();

            foreach (IWord w in currentWords)
            {
                if (word.Text.CompareTo(w.Text) != 0)
                    newWords.Add(w);
                else
                    newWords.Add(new Word(newWord, occurence));
            }
            cloudControl.WeightedWords = newWords;
            cloudControl.Invalidate();
        }

        #endregion

        #region Public Methods (Should only be accessed by the MicrophoneRecorder class)

        public void                             FromMicrophone_WordCloudUpdate(List<string> responses)
        {
            string text = "";
            foreach (string txt in responses)
            {
                text += txt;
            }
            this.ProcessText(text);
        }

        #endregion

        #region Public Methods (Should only be accessed by the InputText form)

        public void                             FromInputText_WordCloudUpdate(string text)
        {
            this.ProcessText(text);
        }

        #endregion

        #region Public Methods (Should only be accessed by the AddWord form)

        public void                             AddNewWordManually(string text, int occurences)
        {
            string res = this.cloudControl.SaveText;
            int i = 0;

            while (i < occurences)
            {
                res += text + " ";
                ++i;
            }
            this.ProcessText(res, true);
        }

        #endregion

        #region Public Static Functions

        public static List<Tuple<string, ImageFormat>>  GetImageFormats()
        {
            List<Tuple<string, ImageFormat>> result = new List<Tuple<string, ImageFormat>>();
            ImageFormat testImageType = ImageFormat.Bmp;
            Type imageType = testImageType.GetType();
            if (imageType != null)
            {
                PropertyInfo[] propInfoList = imageType.GetProperties(BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public);
                int nNumProps = propInfoList.Length;
                for (int i = 0; i < nNumProps; i++)
                {
                    PropertyInfo propInfo = (PropertyInfo)propInfoList[i];
                    ImageFormat imgFormat = (ImageFormat)propInfo.GetValue(null, null);
                    string imgFormatName = propInfo.Name;
                    if (imgFormatName.Length >= 3 && imgFormatName.Length <= 4)
                        result.Add(new Tuple<string, ImageFormat>(imgFormatName, imgFormat));
                }
            }
            return (result);
        }

        public static Color[]                   GetSimpleColorPalette()
        {
            Color[] result = new Color[] {
                Color.DarkRed,
                Color.DarkBlue,
                Color.DarkGreen,
                Color.Navy,
                Color.DarkCyan,
                Color.DarkOrange,
                Color.DarkGoldenrod,
                Color.DarkKhaki,
                Color.Blue,
                Color.Red,
                Color.Green
            };
            return (result);
        }

        public static Color[]                   GetColorPalette(bool allColors = false)
        {
            if (allColors == true)
            {
                // Try to get all installed colors:
                try
                {
                    List<System.Drawing.Color> result = new List<Color>();
                    string[] colors = Enum.GetNames(typeof(System.Drawing.KnownColor));
                    for (int i = 0; i < colors.Length; i++)
                    {
                        Color c = System.Drawing.Color.FromName(colors[i]);
                        if (!c.IsSystemColor && c.Name != "Transparent")
                            result.Add(c);
                    }
                    return (result.ToArray<Color>());
                }
                catch
                {
                    return (GetSimpleColorPalette());
                }
            }
            else
                return (GetSimpleColorPalette());
        }

        public static System.Drawing.Icon       GetProgramIcon()
        {
            return (Properties.Resources.icon);
        }

        #endregion

    }
}
